/*
 * Copyright 2009, Qys Creations 
 * 
 *  This file is part of Chameleon DB.
 *
 *  Chameleon DB is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Chameleon DB is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Chameleon DB.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * */



import java.util.ArrayList;
import java.util.Arrays;

public class db_engine {

	public db_interface_result create_tabel(String sTableName, ArrayList<db_column> oCols, db_database oBase) {


		for (int i = 0; i < oBase.oTables.size(); i++) {
			if (oBase.oTables.get(i).sName.toLowerCase().equals(sTableName))
				return new db_result_error(db_util.DB_ERR_TABLE_ALLREADY_EXISTS, "There is already a table by the name '" + sTableName + "' in the database.");
		}

		for (int i = 0; i < oCols.size(); i++) {
			for (int k = i + 1; k < oCols.size(); k++) {
				if (oCols.get(i).sName.toLowerCase().equals(oCols.get(k).sName.toLowerCase())) {
					return new db_result_error(db_util.DB_ERR_COLUMN_NAME_NOT_UNIQUE, "The column name: " + oCols.get(i).sName + " is not unique.");
				}
			}	
		}

		db_reading_glass oGlass = oBase.getGlass();

		int iGroup = oGlass.createGroup();
		int iDataGroup = oGlass.createGroup();
		int iIndexGroup = oGlass.createGroup();
		
		
		oGlass.seekGroup(iIndexGroup);
		// Commit to disk
		oGlass.begin();
		oGlass.writeInt(0);
		
		oGlass.seekGroup(iDataGroup);
		// Commit to disk
		oGlass.writeInt(0);

		oGlass.seekGroup(1);

		// Write to table definition pointer group

		// Needs to be locked
		long lPosition = oGlass.length();
		int iTableCount = oGlass.readInt();
		oGlass.seek(0);
		oGlass.writeInt(iTableCount + 1);

		oGlass.seek(lPosition);
		oGlass.writeInt(iGroup);

		// Lock out

		// Write table
		oGlass.seekGroup(iGroup);
		oGlass.writeLong(db_util.DB_TABLE_STATUS_NORMAL);
		oGlass.writeStr(sTableName, db_util.DB_BYTE_LENGTH);
		oGlass.writeLong(iDataGroup);
		oGlass.writeLong(iIndexGroup);
		oGlass.writeInt(oCols.size());

		for (int i = 0; i < oCols.size(); i++) {

			oGlass.writeStr(oCols.get(i).sName, db_util.DB_BYTE_LENGTH);

			oGlass.writeLong(oCols.get(i).lType);
			oGlass.writeLong(oCols.get(i).lLength);

			if (oCols.get(i).bNotNull)
				oGlass.writeInt(1);
			else
				oGlass.writeInt(0);
			if (oCols.get(i).bPrimaryKey)
				oGlass.writeInt(1);
			else
				oGlass.writeInt(0);
			if (oCols.get(i).bIdentity)
				oGlass.writeInt(1);
			else
				oGlass.writeInt(0);
			oGlass.writeLong(oCols.get(i).lIndentitySeed);
			oGlass.writeLong(oCols.get(i).lIdentityIncrement);

		}
		oGlass.commit();
		oBase.oTables.add(new db_table(sTableName, iGroup, iDataGroup, iIndexGroup, new ArrayList<db_index>(), oCols));

		return new db_result_message(db_util.DB_MESSAGE_OK, "Table '" + sTableName + "' has been created.");
	}


	public db_interface_result insert_into_table (String sTableName, ArrayList<String> oCols, ArrayList<Integer> oDataTypes, ArrayList<String> oVals, db_database oBase) {
		db_table oTable = null;
		for (int i = 0; i < oBase.oTables.size(); i++) {
			if (oBase.oTables.get(i).sName.toLowerCase().equals(sTableName.toLowerCase())) {
				oTable = oBase.oTables.get(i);
				break;
			}
		}

		if (oTable == null)
			return new db_result_error(db_util.DB_ERR_TABLE_NOT_FOUND, "The table '" + sTableName + "' does not exist in this database.");

		if (oCols.size() != oVals.size()) {
			return new db_result_error(db_util.DB_ERR_COL_VAL_NUMBER_MISMATCH, "The number of columns does not match the number of values.");
		}

		for (int i = 0; i < oCols.size(); i++) {
			db_column oCol = oTable.oGetColumn(oCols.get(i));
			if (oCol == null)
				return new db_result_error(db_util.DB_ERR_COLUMN_NOT_FOUND, "The column '" + oCols.get(i) + "' does not exist in '" + sTableName + "'.");
			if (oCol.bIdentity) {
				return new db_result_error(db_util.DB_ERR_CANNOT_INSERT_IDENTITY, "The column '" + oCols.get(i) + "' is an identity, so custom values cannot be inserted.");
			}
		}
		
		db_reading_glass oGlass = oBase.getGlass();
		
		oGlass.seekGroup(oTable.lDataGroup);
		oGlass.seek(oGlass.length());

		for (int i = 0; i < oTable.oColumns.size(); i++) {
			if (oTable.oColumns.get(i).bIdentity) {
				oCols.add(oTable.oColumns.get(i).sName);
				oDataTypes.add((int)oTable.oColumns.get(i).lType);
				long lIdentity = (oGlass.length()/oTable.iRowSize) + oTable.oColumns.get(i).lIdentityIncrement + + oTable.oColumns.get(i).lIndentitySeed -1;
				oVals.add(lIdentity + "");
			}
		}
		
		// Write row data to temp object
		db_bytes oRow = new db_bytes(oTable.iRowSize);
		oRow.write(db_util.DB_ROW_STATUS_NORMAL);
		for (int i = 0; i < oCols.size(); i++) {
			oRow.seek(oTable.iColumnPosition(oCols.get(i))); 
			if (oDataTypes.get(i).intValue() == db_util.DB_DATATYPE_INT)
				oRow.write(Integer.parseInt(oVals.get(i)));
			if (oDataTypes.get(i).intValue() == db_util.DB_DATATYPE_VARCHAR) {
				oRow.write(oVals.get(i));	
			}
		}
		
		long lTest = oGlass.position();
		oGlass.begin();
		oGlass.writeBytes(oRow.getBytes());
		oGlass.commit();
		oGlass.seek(lTest);
		if (oGlass.readInt() != db_util.DB_ROW_STATUS_NORMAL)
			System.out.println("FAIL FAIL FAIL, wrong row status");
		//if (oGlass.readInt() != 44)
		//	System.out.println("FAIL FAIL FAIL, wrong row value0");
		//if (!oGlass.readStr(128).equals("my@email.com"))
		//	System.out.println("FAIL FAIL FAIL, wrong row value1");
		//if (!oGlass.readStr(128).equals("Mathias Dam"))
		//	System.out.println("FAIL FAIL FAIL, wrong row value2");
		//if (oGlass.readInt() != 66)
		//	System.out.println("FAIL FAIL FAIL, wrong row value3");
		return new db_result_message(db_util.DB_MESSAGE_OK, "1 row inserted");
	}

	public db_interface_result select_from_table(String sTableName, ArrayList<String> oCols, ArrayList<db_where_condition> oConditions, db_database oBase) {
		db_result_dataset oData = null;
		// Error checking, does table and columns exist?
		db_table oTable = null;
		for (int i = 0; i < oBase.oTables.size(); i++) {
			if (oBase.oTables.get(i).sName.toLowerCase().equals(sTableName.toLowerCase())) {
				oTable = oBase.oTables.get(i);
				break;
			}

		}
		if (oTable == null)
			return new db_result_error(db_util.DB_ERR_TABLE_NOT_FOUND, "The table '" + sTableName + "' does not exist in this database.");
		ArrayList<db_column> oSelectCols = new ArrayList<db_column>();
		for (int i = 0; i < oCols.size(); i++) {
			if (oCols.get(i).equals("*")) {
				for (int k = 0; k < oTable.oColumns.size(); k++) {
					oSelectCols.add(oTable.oColumns.get(k));
				}
			} else {
				db_column oColumn = oTable.oGetColumn(oCols.get(i));
				if (oColumn != null)
					oSelectCols.add(oColumn);
				else
					return new db_result_error(db_util.DB_ERR_COLUMN_NOT_FOUND, "The column '" + oCols.get(i) + "' does not exist in '" + sTableName + "'.");
			}
		}

		// Prepare where conditions

		// Type checking
		for (int p = 0; p < oConditions.size(); p++){

			if (oConditions.get(p).iType1 == db_util.DB_DATATYPE_COLUMN) {
				db_column oCol = oTable.oGetColumn(oConditions.get(p).sValue1);
				if (oCol == null)
					return new db_result_error(db_util.DB_ERR_COLUMN_NOT_FOUND, "The column '" + oConditions.get(p).sValue1 + "' does not exist in '" + sTableName + "'.");
				oConditions.get(p).oColumn1 = oCol;
			}
			if (oConditions.get(p).iType1 != oConditions.get(p).iType2) {
				if (oConditions.get(p).iType1 == db_util.DB_DATATYPE_COLUMN && oConditions.get(p).iType2 != db_util.DB_DATATYPE_COLUMN) {
					if (oConditions.get(p).oColumn1.lType != oConditions.get(p).iType2)
						return new db_result_error(db_util.DB_ERR_TYPE_MISMATCH, "Type mismatch.");
				} else
					return new db_result_error(db_util.DB_ERR_TYPE_MISMATCH, "Type mismatch.");
			}

			if (oConditions.get(p).iType2 == db_util.DB_DATATYPE_COLUMN) {
				db_column oCol = oTable.oGetColumn(oConditions.get(p).sValue2);
				if (oCol == null)
					return new db_result_error(db_util.DB_ERR_COLUMN_NOT_FOUND, "The column '" + oConditions.get(p).sValue2 + "' does not exist in '" + sTableName + "'.");
				oConditions.get(p).oColumn2 = oCol;
			}

			if (oConditions.get(p).iType2 == db_util.DB_DATATYPE_COLUMN) {
				if (oConditions.get(p).oColumn2.lType != oConditions.get(p).iType1 && oConditions.get(p).iType1 != db_util.DB_DATATYPE_COLUMN)
					return new db_result_error(db_util.DB_ERR_TYPE_MISMATCH, "Type mismatch.");
			} 

			if (oConditions.get(p).iType1 == db_util.DB_DATATYPE_COLUMN && oConditions.get(p).iType2 == db_util.DB_DATATYPE_COLUMN)
				if (oConditions.get(p).oColumn2.lType != oConditions.get(p).oColumn1.lType)
					return new db_result_error(db_util.DB_ERR_TYPE_MISMATCH, "Type mismatch.");
		}


		// Prepare row buffer object
		int iOutRowSize = 0;
		for (int i = 0; i < oSelectCols.size(); i++) {
			iOutRowSize += oSelectCols.get(i).lLength;

			if (oSelectCols.get(i).lType == db_util.DB_DATATYPE_VARCHAR)
				iOutRowSize+=4;
		}

		db_reading_glass oGlass = oBase.getGlass();
		oGlass.seekGroup(oTable.lDataGroup);
		oGlass.seek(4);

		// These loops are the innermost of the select statement and should be ultra fast
		// Followed by lots and lots of code ^^
	
		ArrayList<byte[]> oOut = new ArrayList<byte[]>();
		long lRowCnt = (oGlass.length()-4)/ oTable.iRowSize;
		for (int i = 0; i < lRowCnt; i++) {
			if (oGlass.readInt() == db_util.DB_ROW_STATUS_NORMAL) {
				byte[] oRow = oGlass.readBytes(oTable.iRowSize-4);
				byte[] bCond1 = null;
				byte[] bCond2 = null;
				boolean bWhere = true;
				for (int k = 0; k < oConditions.size(); k++) {

					if (oConditions.get(k).iType1 == db_util.DB_DATATYPE_INT) {
						bCond1 = db_bytes.intToByteArray(Integer.parseInt(oConditions.get(k).sValue1));
					}
					if (oConditions.get(k).iType1 == db_util.DB_DATATYPE_VARCHAR) {
						bCond1 = oConditions.get(k).sValue1.getBytes();
					}
					
					if (oConditions.get(k).iType1 == db_util.DB_DATATYPE_COLUMN) {
						if (oConditions.get(k).oColumn1.lType == db_util.DB_DATATYPE_INT) {
							bCond1 = new byte[(int)(oConditions.get(k).oColumn1.lLength)];
							System.arraycopy(oRow, oTable.iColumnPosition(oConditions.get(k).oColumn1.sName)-4, bCond1, 0, bCond1.length);
						}
						if (oConditions.get(k).oColumn1.lType == db_util.DB_DATATYPE_VARCHAR) {
							byte[] bSize = new byte[4];
							System.arraycopy(oRow, oTable.iColumnPosition(oConditions.get(k).oColumn1.sName)-4, bSize, 0, 4);
							bCond1 = new byte[db_bytes.byteArrayToInt(bSize)];
							System.arraycopy(oRow, oTable.iColumnPosition(oConditions.get(k).oColumn1.sName), bCond1, 0, bCond1.length);
						}					} 
					if (oConditions.get(k).iType2 == db_util.DB_DATATYPE_INT) {
						bCond2 = db_bytes.intToByteArray(Integer.parseInt(oConditions.get(k).sValue2));
					}
					if (oConditions.get(k).iType2 == db_util.DB_DATATYPE_VARCHAR) {
						bCond2 = oConditions.get(k).sValue2.getBytes();
					}
					if (oConditions.get(k).iType2 == db_util.DB_DATATYPE_COLUMN) {
						if (oConditions.get(k).oColumn2.lType == db_util.DB_DATATYPE_INT) {
							bCond2 = new byte[(int)(oConditions.get(k).oColumn2.lLength)];
							System.arraycopy(oRow, oTable.iColumnPosition(oConditions.get(k).oColumn2.sName)-4, bCond2, 0, bCond2.length);
						}
						if (oConditions.get(k).oColumn2.lType == db_util.DB_DATATYPE_VARCHAR) {
							byte[] bSize = new byte[4];
							System.arraycopy(oRow, oTable.iColumnPosition(oConditions.get(k).oColumn2.sName)-4, bSize, 0, 4);
							bCond2 = new byte[db_bytes.byteArrayToInt(bSize)];
							System.out.println(bCond2.length);
							System.arraycopy(oRow, oTable.iColumnPosition(oConditions.get(k).oColumn2.sName), bCond2, 0, bCond2.length);
						}
					} 
				}
				//System.out.println(db_bytes.byteArrayToInt(bCond1) + " " + db_bytes.byteArrayToInt(bCond2));
				//System.out.println(bCond1);
				//System.out.println(new String(bCond1));
				bWhere = Arrays.equals(bCond1,bCond2);
				if (bWhere) {

					byte[] oRowOut = new byte[iOutRowSize];
					int iPos = 0;

					for (int k = 0; k < oSelectCols.size(); k++) {
						//System.out.println(oRow.length + " " + (oTable.iColumnPosition(oSelectCols.get(k).sName)-4) + " " + (int)oSelectCols.get(k).lLength + " ");
						if (oSelectCols.get(k).lType == db_util.DB_DATATYPE_INT)
							System.arraycopy(oRow, oTable.iColumnPosition(oSelectCols.get(k).sName)-4, oRowOut, iPos, (int)(oSelectCols.get(k).lLength));

						if (oSelectCols.get(k).lType == db_util.DB_DATATYPE_VARCHAR) {
							System.arraycopy(oRow, oTable.iColumnPosition(oSelectCols.get(k).sName)-4, oRowOut, iPos, (int)(oSelectCols.get(k).lLength+4));	
							iPos += 4;
						}
						iPos += oSelectCols.get(k).lLength;
					}
					//System.out.println(new String (oRowOut));
					oOut.add(oRowOut);
				}
			} else 				
				oGlass.seek(oGlass.position() + oTable.iRowSize-4);
		}
		
		db_bytes oBytes = new db_bytes( oOut.size() * iOutRowSize);
		for (int i = 0; i < oOut.size(); i++){
			oBytes.write(oOut.get(i));
		}
		oData = new db_result_dataset(oSelectCols, oOut.size(), oBytes.getBytes());
		return oData; 
	}
	
	
	
	
	public db_interface_result delete_from_table(String sTableName,  ArrayList<db_where_condition> oConditions, db_database oBase) {
		long lDeletedRowCnt = 0;

		// Error checking, does table and columns exist?
		db_table oTable = null;
		for (int i = 0; i < oBase.oTables.size(); i++) {
			if (oBase.oTables.get(i).sName.toLowerCase().equals(sTableName.toLowerCase())) {
				oTable = oBase.oTables.get(i);
				break;
			}

		}
		if (oTable == null)
			return new db_result_error(db_util.DB_ERR_TABLE_NOT_FOUND, "The table " + sTableName + " does not exist in this database.");


		// Prepare where conditions

		// Type checking
		for (int p = 0; p < oConditions.size(); p++){

			if (oConditions.get(p).iType1 == db_util.DB_DATATYPE_COLUMN) {
				db_column oCol = oTable.oGetColumn(oConditions.get(p).sValue1);
				if (oCol == null)
					return new db_result_error(db_util.DB_ERR_COLUMN_NOT_FOUND, "The column \"" + oConditions.get(p).sValue1 + "\" does not exist in " + sTableName + ".");
				oConditions.get(p).oColumn1 = oCol;
			}
			if (oConditions.get(p).iType1 != oConditions.get(p).iType2) {
				if (oConditions.get(p).iType1 == db_util.DB_DATATYPE_COLUMN && oConditions.get(p).iType2 != db_util.DB_DATATYPE_COLUMN) {
					if (oConditions.get(p).oColumn1.lType != oConditions.get(p).iType2)
						return new db_result_error(db_util.DB_ERR_TYPE_MISMATCH, "Type mismatch.");
				} else
					return new db_result_error(db_util.DB_ERR_TYPE_MISMATCH, "Type mismatch.");
			}

			if (oConditions.get(p).iType2 == db_util.DB_DATATYPE_COLUMN) {
				db_column oCol = oTable.oGetColumn(oConditions.get(p).sValue2);
				if (oCol == null)
					return new db_result_error(db_util.DB_ERR_COLUMN_NOT_FOUND, "The column \"" + oConditions.get(p).sValue2 + "\" does not exist in " + sTableName + ".");
				oConditions.get(p).oColumn2 = oCol;
			}

			if (oConditions.get(p).iType2 == db_util.DB_DATATYPE_COLUMN) {
				if (oConditions.get(p).oColumn2.lType != oConditions.get(p).iType1 && oConditions.get(p).iType1 != db_util.DB_DATATYPE_COLUMN)
					return new db_result_error(db_util.DB_ERR_TYPE_MISMATCH, "Type mismatch.");
			} 

			if (oConditions.get(p).iType1 == db_util.DB_DATATYPE_COLUMN && oConditions.get(p).iType2 == db_util.DB_DATATYPE_COLUMN)
				if (oConditions.get(p).oColumn2.lType != oConditions.get(p).oColumn1.lType)
					return new db_result_error(db_util.DB_ERR_TYPE_MISMATCH, "Type mismatch.");
		}

		db_reading_glass oGlass = oBase.getGlass();
		oGlass.seekGroup(oTable.lDataGroup);
		oGlass.seek(4);

		// These loops are the innermost of the delete statement and should be ultra fast
		// Followed by lots and lots of code ^^
		// soo slow code
		
		for (int i = 0; i < (oGlass.length()-4)/ oTable.iRowSize; i++) {
			
			if (oGlass.readInt() == db_util.DB_ROW_STATUS_NORMAL) {
				byte[] oRow = oGlass.readBytes(oTable.iRowSize-4);
				byte[] bCond1 = null;
				byte[] bCond2 = null;
				boolean bWhere = true;
				ArrayList<Boolean> oBools = new ArrayList<Boolean>();
				for (int k = 0; k < oConditions.size(); k++) {

					if (oConditions.get(k).iType1 == db_util.DB_DATATYPE_INT) {
						bCond1 = db_bytes.intToByteArray(Integer.parseInt(oConditions.get(k).sValue1));
					}
					if (oConditions.get(k).iType1 == db_util.DB_DATATYPE_VARCHAR) {
						bCond1 = oConditions.get(k).sValue1.getBytes();
					}
					if (oConditions.get(k).iType1 == db_util.DB_DATATYPE_COLUMN) {
						if (oConditions.get(k).oColumn1.lType == db_util.DB_DATATYPE_INT) {
							bCond1 = new byte[(int)(oConditions.get(k).oColumn1.lLength)];
							System.arraycopy(oRow, oTable.iColumnPosition(oConditions.get(k).oColumn1.sName)-4, bCond1, 0, bCond1.length);
						}
						if (oConditions.get(k).oColumn1.lType == db_util.DB_DATATYPE_VARCHAR) {
							byte[] bSize = new byte[4];
							System.arraycopy(oRow, oTable.iColumnPosition(oConditions.get(k).oColumn1.sName)-4, bSize, 0, 4);
							bCond1 = new byte[db_bytes.byteArrayToInt(bSize)];
							System.arraycopy(oRow, oTable.iColumnPosition(oConditions.get(k).oColumn1.sName), bCond1, 0, bCond1.length);
						}					} 
					if (oConditions.get(k).iType2 == db_util.DB_DATATYPE_INT) {
						bCond2 = db_bytes.intToByteArray(Integer.parseInt(oConditions.get(k).sValue2));
					}
					if (oConditions.get(k).iType2 == db_util.DB_DATATYPE_VARCHAR) {
						bCond2 = oConditions.get(k).sValue2.getBytes();
					}
					if (oConditions.get(k).iType2 == db_util.DB_DATATYPE_COLUMN) {
						if (oConditions.get(k).oColumn2.lType == db_util.DB_DATATYPE_INT) {
							bCond2 = new byte[(int)(oConditions.get(k).oColumn2.lLength)];
							System.arraycopy(oRow, oTable.iColumnPosition(oConditions.get(k).oColumn2.sName)-4, bCond2, 0, bCond2.length);
						}
						if (oConditions.get(k).oColumn2.lType == db_util.DB_DATATYPE_VARCHAR) {
							byte[] bSize = new byte[4];
							System.arraycopy(oRow, oTable.iColumnPosition(oConditions.get(k).oColumn2.sName)-4, bSize, 0, 4);
							bCond2 = new byte[db_bytes.byteArrayToInt(bSize)];
							System.out.println(bCond2.length);
							System.arraycopy(oRow, oTable.iColumnPosition(oConditions.get(k).oColumn2.sName), bCond2, 0, bCond2.length);
						}
					}
					boolean bResult = false;
					if (oConditions.get(k).iType1 != db_util.DB_DATATYPE_VARCHAR) {
						switch (oConditions.get(k).iCompareOperator) {
							case 1: bResult = (db_bytes.byteArrayToInt(bCond1) == db_bytes.byteArrayToInt(bCond2) ); 
							case 2: bResult = (db_bytes.byteArrayToInt(bCond1) <= db_bytes.byteArrayToInt(bCond2)); 
							case 3: bResult = (db_bytes.byteArrayToInt(bCond1) >= db_bytes.byteArrayToInt(bCond2)); 
							case 4: bResult = (db_bytes.byteArrayToInt(bCond1) < db_bytes.byteArrayToInt(bCond2)); 
							case 5: bResult = (db_bytes.byteArrayToInt(bCond1) > db_bytes.byteArrayToInt(bCond2));
						}
					} else
						bResult = Arrays.equals(bCond1,bCond2);

					oBools.add(bResult);
					
				}
				bWhere = true;
				for (int z = 0; z < oBools.size(); z++) {
					if (!oBools.get(z))
						bWhere = false;
				}
				
				if (bWhere) {

					oGlass.seek(oGlass.position()-oTable.iRowSize);
					
					// Mark row as deleted
					oGlass.writeInt(db_util.DB_ROW_STATUS_DELETED);
					
					oGlass.seek(oGlass.position() + oTable.iRowSize-4);
					lDeletedRowCnt++;
				}
			} else 				
				oGlass.seek(oGlass.position() + oTable.iRowSize-4);

		}

		return new db_result_message(db_util.DB_MESSAGE_OK, lDeletedRowCnt + " rows was deleted");
	}
	
	public db_interface_result create_index(String sTableName, String sIndexName, ArrayList<String> oCols, ArrayList<Integer> oSortOrder,  db_database oBase) {
		db_table oTable = null;
		
		for (int i = 0; i < oBase.oTables.size(); i++) {
			if (oBase.oTables.get(i).sName.toLowerCase().equals(sTableName.toLowerCase())) {
				oTable = oBase.oTables.get(i);
				break;
			}
		}
		
		if (oTable == null)
			return new db_result_error(db_util.DB_ERR_TABLE_NOT_FOUND, "The table " + sTableName + " does not exist in this database.");
		
		for (int i = 0; i < oCols.size(); i++) {
				db_column oColumn = oTable.oGetColumn(oCols.get(i));
				if (oColumn == null)
					return new db_result_error(db_util.DB_ERR_COLUMN_NOT_FOUND, "The column '" + oCols.get(i) + "' does not exist in '" + sTableName + "'.");
		}
		
		for (int i = 0; i < oTable.oIndex.size(); i++) {
			if (sIndexName.toLowerCase().equals(oTable.oIndex.get(i).sName.toLowerCase()))
				return new db_result_error(db_util.DB_ERR_INDEX_ALLREADY_EXISTS, "There is already an index called '" + sIndexName + "' on the table '" + sTableName + "'.");
		}
		db_reading_glass oGlass = oBase.getGlass();
		
		int iNewIndexGroup = oGlass.createGroup();
		oGlass.seekGroup(iNewIndexGroup);
		
		// Write index definition
		oGlass.writeInt(db_util.DB_INDEX_STATUS_NORMAL);
		oGlass.writeStr(sIndexName, 128);
		oGlass.writeInt(oCols.size());
		for (int i = 0; i < oCols.size(); i++) {
			oGlass.writeStr(oCols.get(i));
			oGlass.writeInt(oSortOrder.get(i));
		}
		
		// Create new index data group
		int iNewIndexDataGroup = oGlass.createGroup();
		oGlass.writeInt(iNewIndexDataGroup);
		oGlass.seekGroup(iNewIndexDataGroup);
		oGlass.writeInt(0);
		
		// this section should be atomic
		oGlass.seekGroup(oTable.lIndexGroup);
		oGlass.seek(oGlass.length());
		oGlass.writeInt(iNewIndexGroup);
		// section out
		
		return new db_result_message(db_util.DB_MESSAGE_OK, "The index '" + sIndexName + "' was created");
	}
	
	public db_interface_result update_table(String sTableName, ArrayList<String> oCols, ArrayList<Integer> oDataTypes, ArrayList<String> oVals, ArrayList<db_where_condition> oConditions, db_database oBase) {
		db_table oTable = null;
		
		for (int i = 0; i < oBase.oTables.size(); i++) {
			if (oBase.oTables.get(i).sName.toLowerCase().equals(sTableName.toLowerCase())) {
				oTable = oBase.oTables.get(i);
				break;
			}
		}
		
		if (oTable == null)
			return new db_result_error(db_util.DB_ERR_TABLE_NOT_FOUND, "The table " + sTableName + " does not exist in this database.");
		
		for (int i = 0; i < oCols.size(); i++) {
			db_column oColumn = oTable.oGetColumn(oCols.get(i));
			if (oColumn == null)
				return new db_result_error(db_util.DB_ERR_COLUMN_NOT_FOUND, "The column '" + oCols.get(i) + "' does not exist in '" + sTableName + "'.");
		}
		
		for (int i = 0; i < oCols.size(); i++) {
			for (int k = i + 1; k < oCols.size(); k++) {
				if (oCols.get(i).toLowerCase().equals(oCols.get(k).toLowerCase())) {
					return new db_result_error(db_util.DB_ERR_COLUMN_NAME_NOT_UNIQUE, "The column name: " + oCols.get(i) + " is not unique.");
				}
			}	
		}
		
		db_reading_glass oGlass = oBase.getGlass();
		oGlass.seekGroup(oTable.lDataGroup);
		oGlass.seek(4);
		oGlass.begin();
		
		// Hacks on:
		long lRowsCnt = (oGlass.length()-4)/ oTable.iRowSize;
		for (int i = 0; i < lRowsCnt; i++) {
			
			if (oGlass.readInt() == db_util.DB_ROW_STATUS_NORMAL) {
				
				
				byte[] oRowBytes = oGlass.readBytes(oTable.iRowSize-4);
				oGlass.seek(oGlass.position() - (oTable.iRowSize-4));
				
				db_bytes oRow = new db_bytes(oRowBytes);
				for (int k = 0; k < oCols.size(); k++) {
					oRow.seek(oTable.iColumnPosition(oCols.get(k))-4);
					if (oDataTypes.get(k) == db_util.DB_DATATYPE_INT)
						oRow.write(Integer.parseInt(oVals.get(k)));
					if (oDataTypes.get(k) == db_util.DB_DATATYPE_VARCHAR)
						oRow.write(oVals.get(k));
				}
				
				oGlass.seek(oGlass.position() - (oTable.iRowSize-4));
				
				oGlass.writeBytes(oRow.getBytes());
				
			} else 				
				oGlass.seek(oGlass.position() + oTable.iRowSize-4);
			
		}
		
		oGlass.commit();
		return new db_result_message(db_util.DB_MESSAGE_OK, "Update ok " + oCols.size());
	}
}
